//
// Created by huayang on 17-8-14.
//

#include "tdss.h"

#include <fcntl.h>
#include <unistd.h>
#include <string.h>

Tdss::Tdss() {

}

Tdss::~Tdss() {
  Umap();
  Release();
}

int Tdss::CreateTdssFile(const char* name, size_t cache_size) {
  int fd = open(name, O_CREAT | O_TRUNC | O_RDWR, S_IRUSR | S_IWUSR);
  if (fd == -1) {
    return -1;
  }
  cache_size_ = cache_size;
  int trunc_ok = ftruncate(fd, cache_size_);
  if (trunc_ok) {
    std::cerr << "trunc failed" << std::endl;
    return TdssErrCode::kFailed;
  }
  return fd;
}

void* Tdss::MapTdssFile(int fd, size_t len, size_t offset) {
  return mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, offset);
}

Header* Tdss::ParseHeader(void* addr) {
  header_ = (Header *) addr;
  header_->section_offsets[0] = sizeof(Header);
  std::cout << "header size : " << sizeof(Header) << std::endl;
  return header_;
}

Sections* Tdss::ParseSections(void* addr) {
  char* ch_sections = (char*)addr + sizeof(Header);
  curr_sections_ = (Sections *) ch_sections;
  curr_sections_->sections[0].state = kUsed;
  curr_sections_->sections[0].segments_offset = sizeof(Header) + sizeof(Sections);
  std::cout << "section size : " << sizeof(SectionNode) << std::endl;
  return curr_sections_;
}

Segments* Tdss::ParseSegments(void* addr) {
  char* ch_segments = (char*)addr + sizeof(Header) + sizeof(Sections);
  curr_segments_ = (Segments *) ch_segments;
  std::cout << "segment size : " << sizeof(SegmentNode) << std::endl;
  return curr_segments_;
}

TdssErrCode Tdss::Init(const char* name, size_t size, std::string magic, std::string version) {
  if (magic.size() > 4 || version.size() > 4) {
    std::cerr << "magic or version err !" << std::endl;
    return TdssErrCode::kFailed;
  }
  fd_ = CreateTdssFile(name, size);
  if (fd_ == -1) {
    std::cerr << "Create file failed !" << std::endl;
    return TdssErrCode::kFailed;
  }

  size_t init_len = sizeof(Header) + sizeof(Sections) + sizeof(Segments);
  start_ = MapTdssFile(fd_, init_len, 0);
  if (start_ == MAP_FAILED) {
    std::cerr << "Map file failed !" << std::endl;
    return TdssErrCode::kFailed;
  }

  ParseHeader(start_);
  ParseSections(start_);
  ParseSegments(start_);
  const char* c_magic = magic.c_str();
  const char* c_version = version.c_str();
  memcpy(header_->magic, c_magic, strlen(c_magic));
  memcpy(header_->version, c_version, strlen(c_version));

  header_->section_offsets[0] = sizeof(Header);
  header_->section_offsets[1] = 0;
  header_->section_offsets[2] = 0;
  header_->section_offsets[3] = 0;
  global_pos_ = sizeof(Header) + sizeof(Sections) + sizeof(Segments);
  return TdssErrCode::kOk;
}

void Tdss::Release() {
  //
  std::cout << "Release" << std::endl;
}

void Tdss::Umap() {
  //munmap(start_, cache_size_);
  munmap(start_, sizeof(Header));
  munmap(curr_sections_, sizeof(Sections));
  munmap(curr_segments_, sizeof(Segments));
  std::cout << "Umap" << std::endl;
}

int Tdss::AddData(BaseData& data) {
  bool has_empty = false;
  for (int idx = 0; idx < kEachSegments; ++idx) {
    if (curr_segments_->segments[idx].state != kUsed) {
      has_empty = true;
      break;
    }
  }
  if (!has_empty) {
    std::cout << "This segments is full !" << std::endl;
    //Gen Segments
    int err_code = GenSegments();
    if (err_code == TdssErrCode::kErrFull) {
      std::cerr << "Can't gen segments" << std::endl;
      return TdssErrCode::kErrFull;
    }
  }

  int offset = global_pos_;
  std::cout << "AddData to : " << offset << std::endl;
  data.segment.offset = offset;
  int ok = AddSegmentItem(data.segment);
  if (ok != 0) {
    return TdssErrCode ::kFailed;
  }
  //Save data at start of offset
  int test_len = sizeof(Header) + sizeof(Sections) + sizeof(Segments);
  //memcpy((char*)start_ + offset, data.data, strlen(data.data));
  lseek(fd_, offset, SEEK_SET);
  write(fd_, data.data, strlen(data.data));
  //memcpy((char*)start_ + test_len, data.data, strlen(data.data));
  global_pos_ += data.segment.total_length;
  std::cout << "After add , move pos : " << global_pos_ << std::endl;
  std::cout << "-----------------------------------------" << std::endl;
  return TdssErrCode::kOk;
}

int Tdss::GenSegments() {
  int empty_section_idx = FindEmptySection();
  std::cout << "GenSegments, empty section idx : " << empty_section_idx << std::endl;
  if (empty_section_idx == TdssErrCode::kFailed) {
    int err_code = GenSections();
    if (err_code == TdssErrCode::kErrFull) {
      std::cerr << "Gen Sections failed !" << std::endl;
      return TdssErrCode::kErrFull;
    } else {
      empty_section_idx = 0;
    }
  }

  size_t segments_size = sizeof(Segments);
  int page_size = getpagesize();
  //calculate next Segments offset
  int next_segment_offset;
  int pages = global_pos_ / page_size;
  if ((global_pos_ % page_size) > 0) {
    next_segment_offset = (pages + 1) * page_size;
  } else {
    next_segment_offset = global_pos_;
  }

  //int next_segment_offset = global_pos_;
  std::cout << "We have used : " << next_segment_offset << std::endl;
  if (next_segment_offset >= (cache_size_ - segments_size)) {
    std::cerr << "Gen Segments. Cache file full !" << std::endl;
    //todo Notify
    return TdssErrCode::kErrFull;
  }

  std::cout << "next_segment_offset " << next_segment_offset << std::endl;
  //curr_segments_ = (Segments *) ((char*)start_ + next_segment_offset);
  curr_segments_ = (Segments *) MapTdssFile(fd_, segments_size, next_segment_offset);
  curr_sections_->sections[empty_section_idx].state = kUsed;
  curr_sections_->sections[empty_section_idx].segments_offset = next_segment_offset;
  //global_pos_ += segments_size;
  global_pos_ =  (next_segment_offset + segments_size);
  std::cout << "Gen Segments end, global_pos : " << global_pos_ << std::endl;
  return TdssErrCode::kOk;
}

int Tdss::GenSections() {
  int sections_idx = FindEmptySectionsInHeader();
  std::cout << "Gen Sections idx : " << sections_idx << std::endl;
  if (sections_idx == -1) {
    std::cerr << "Gen Sections failed !" << std::endl;
    return TdssErrCode::kErrFull;
  }

  int segments_size = sizeof(Segments);
  int sections_size = sizeof(Sections);
  int page_size = getpagesize();
  //int next_section_offset = global_pos_;

  //calculate next Segments offset
  int next_section_offset ;
  int pages = global_pos_ / page_size;
  if ((global_pos_ % page_size) > 0) {
    next_section_offset  = (pages + 1) * page_size;
  } else {
    next_section_offset  = global_pos_;
  }

  size_t left_space = cache_size_ - segments_size - sections_size;
  if (next_section_offset >= left_space) {
    std::cerr << "Gen Sections. Cache file full !" << std::endl;
    //todo Notify
    return TdssErrCode::kErrFull;
  }

  std::cout << "next_section_offset : " << next_section_offset << std::endl;
  //curr_sections_ = (Sections *) ((char*)start_ + next_section_offset);
  curr_sections_ = (Sections *) MapTdssFile(fd_, sections_size, next_section_offset);
  //todo update header section
  header_->section_offsets[sections_idx] = next_section_offset;
  //global_pos_ += sections_size;
  global_pos_ = next_section_offset + sections_size;
  std::cout << "Gen Sections end , global_pos_ : " << global_pos_ << std::endl;
  return TdssErrCode::kOk;
}

int Tdss::FindEmptySectionsInHeader() {
  for (int i = 0; i < kSections; ++i) {
    //we init no used == 0
    if (header_->section_offsets[i] == 0)
      return i;
  }
  return TdssErrCode::kFailed;
}

int Tdss::AddSegmentItem(const SegmentNode& segment) {
  int index = FindEmptySegment();
  if (index == -1) {
    std::cerr << "Can't find empty segment." << std::endl;
    return TdssErrCode::kFailed;
  }
  std::cout << "Add segment item : " << index << std::endl;
  curr_segments_->segments[index].state = kUsed;
  memcpy(curr_segments_->segments[index].id, segment.id, strlen(segment.id));
  curr_segments_->segments[index].type = segment.type;
  curr_segments_->segments[index].offset = segment.offset;
  curr_segments_->segments[index].name_length = segment.name_length;
  curr_segments_->segments[index].total_length = segment.total_length;
  std::cout << "Add segment item success !" << std::endl;
  return TdssErrCode::kOk;
}

int Tdss::FindEmptySegment() {
  for (int i = 0; i < kEachSegments; ++i) {
    if (curr_segments_->segments[i].state != kUsed) {
      return i;
    }
  }
  return TdssErrCode::kFailed;
}

int Tdss::FindEmptySection() {
  for (int i = 0; i < kEachSections; ++i) {
    std::cout << "section stat : " << curr_sections_->sections[i].state << std::endl;
    if (curr_sections_->sections[i].state != kUsed) {
      return i;
    }
  }
  return TdssErrCode::kFailed;
}
